public class World
{
  //private int laser_count_max = 3;

  private int Score = 0;

  //private AnimationContext animationContext;

  private int cols;
  private int rows;

  private Spaceship spaceShip;
  private ArrayList aliens;
  private ArrayList bombs;
  private ArrayList lazers;
  private ArrayList blocks;
  private Ufo ufo;

  private Timer timer;

  public int alienCounter, score;
  public boolean alienAllDead;

  private boolean started = false; //passe à TRUE lorsque le jeu a commencé
  PImage istart = loadImage("start.png"); //image de la page de chargement
  private boolean pause = false; // passe à true si le jeu est en pause

    /// Univers de jeu, contient les entités et gère l'animation
  public World(AnimationContext animationContext)
  {
    //animationContext = new AnimationContext();

    // Le timer permet de lancer des projectiles
    timer = new Timer(125);
    timer.start();

    cols = 10;
    rows = 5;

    aliens = new ArrayList();
    bombs = new ArrayList();
    lazers = new ArrayList();
    blocks = new ArrayList();
    ufo = null;

    spaceShip = new Spaceship(500, 900, animationContext);

    alienCounter = 47;
    alienAllDead = false;

    /// Instanciation et placement des aliens
    AlienLocator locator = new AlienLocator();

    for (int i = 0; i < locator.Coordinates.size(); i++)
    {
      Position position = (Position) locator.Coordinates.get(i);
      Alien alien = new Alien(position.X * 45, position.Y * 50 + 70, position.Type);
      aliens.add(alien);
    }

    // Création des blocs
    for (int x = 0; x < 4; x++)
    {    
      blocks.add(new Block(x * (width / 4) + width / 10, height - 200));
    }
  }

  /// Mise à jour de la position des entités
  public void updatePositions()
  {
    // Vérifie si un des alien est hors de la zone
    // si oui => on change de direction et on shift
    //           cad, on positionn MustShiftAlien à true
    checkBorders();

    // Déplacement du spaceship (suivi de la souris)
    if (controlIsMouse)
      spaceShip.updatePosition();
    else
      spaceShip.updatePosition(animationContext);

    // Déplacement de toutes les entités (aliens, bombes et lazers)
    updatePositions(aliens);
    updatePositions(bombs);
    updatePositions(lazers);

    // Les aliens sont descendus si besoin, on positionne
    // MustShiftAlien à false
    animationContext.MustShiftAlien = false;

    // Si l'UFO est présent sur l'écran, on le déplace
    if (ufo != null && !ufo.isDead)
    {
      ufo.updatePosition(animationContext);
    }
    else
    {
      // Sinon, on l'instancie peut etre (1 chance sur 200 par frame affichée)
      if (int(random(0, 500)) == 1)
      {
        ufo = new Ufo(0, 30);
      }
    }
  }

  /// Itération sur la liste des entités passée en paramètres pour effectuer
  /// la mise à jour des positions (chaque entité implémente sa fonction de déplacement)
  private void updatePositions(ArrayList lst)
  {
    for (int i=0; i < lst.size(); i++)
    {
      Entity element = (Entity) lst.get(i);
      element.updatePosition(animationContext);
    }
  }

  /// Vérification de l'atteinte des limites de l'écran pour les aliens et projectiles
  private void checkBorders()
  {
    // Shift des aliens vers le bas s'ils touchent la bordure de l'écran
    for (int i=0; i < aliens.size(); i++)
    {
      Alien alien = (Alien) aliens.get(i);

      if (!alien.isDead && alien.isOutsideZone())
      {
        animationContext.AlienDirection = animationContext.AlienDirection * -1;
        animationContext.MustShiftAlien = true;
        return;
      }
    }

    // Si l'UFO touche le bord de l'écran, on le tue
    if (ufo != null && !ufo.isDead && ufo.isOutsideZone())
    {
      ufo.isDead = true;
    }

    // Suppression des lazers qui sont hors de la zone de l'écran
    deleteOutsideEntities(lazers);
    deleteOutsideEntities(bombs);
  }

  /// Suppression des entités hors de l'écran (projectiles)
  private void deleteOutsideEntities(ArrayList lst)
  {
    ArrayList tmp = new ArrayList(lst);

    for (int i=0; i < tmp.size(); i++)
    {
      Entity entity = (Entity) tmp.get(i);

      if (entity.isOutsideZone())
      {
        lst.remove(entity);
      }
    }
  }

  /// Tir de projectiles pour les Aliens limité à un tir maximum pour tous les aliens sous 200ms
  void fireAlienProjectiles()
  {
    for (int i=0; i < aliens.size(); i++)
    {
      Alien alien = (Alien) aliens.get(i);

      if (!alien.isDead)
      {
        boolean fireBomb = int(random(0, 50)) == 5;

        if (timer.isFinished() && fireBomb)
        {
          bombs.add(new Bomb(alien.xpos + (alien.renderSizeX / 2), alien.ypos + alien.renderSizeY + 10));
          timer.start();
          break;
        }
      }
    }
  }

  /// Tir de projectiles pour le vaisseau
  public void addLazer()
  {
    if (lazers.size() < laser_count_max)
    {
      // le 3eme parametre représente la position du canon, et définit la direction des lasers
      lazers.add(new Lazer(spaceShip.xpos + (spaceShip.renderSizeX / 2) - 5, spaceShip.ypos, (int) spaceShip.cannonPos));
      tirspaceship.trigger();
    }
    if (isCheatOn)
    {
      lazers.add(new Lazer(spaceShip.xpos + (spaceShip.renderSizeX / 2) - 25, spaceShip.ypos, (int) spaceShip.cannonPos));
      //tirspaceship.trigger();
      lazers.add(new Lazer(spaceShip.xpos + (spaceShip.renderSizeX / 2) + 15, spaceShip.ypos, (int) spaceShip.cannonPos));
      //tirspaceship.trigger();
    }
  }

  /// Vérification des collisions projectiles avec vaisseau / aliens
  public void checkCollisions()
  {
    // Vérification des collisions entre blocks et projectiles
    checkBlocksCollisions();

    // Vérification des collisions entre aliens et spaceship
    checkAliensCollisions();

    // Vérification des collisions des bombes avec le vaisseau
    if (checkCollisions(bombs, spaceShip))
    {
      spaceShip.removeLife();
    }


    // Vérification des collisions des aliens avec le vaisseau
    if (checkCollisions(aliens, spaceShip))
    {
      spaceShip.removeLife();
    }

    if (ufo != null && !ufo.isDead && checkCollisions(lazers, ufo))
    {
      ufo.isDead = true;
      score += 1000;
    }

    // Vérification des collisions des lazers avec les aliens
    for (int i=0; i < aliens.size(); i++)
    {
      Alien alien = (Alien) aliens.get(i);

      if (!alien.isDead && checkCollisions(lazers, alien))
      {
        alien.isDead = true;
        alienCounter -= 1;
        score += 100;
        println(alienCounter);
        if (alienCounter <= 0)
        {
          alienAllDead = true;
        }
      }
    }
  }

  /// Fonction de test des collisions entre les blocs (bases) et les différents éléments du jeu
  /// à savoir, bombes, lazers et aliens
  private void checkBlocksCollisions()
  {
    for (int i=0; i < blocks.size(); i++)
    {
      Block block = (Block) blocks.get(i);

      ArrayList tmp = new ArrayList(bombs);

      // Test de collision des blocs avec les bombes
      for (int j=0; j < tmp.size(); j++)
      {
        Entity bomb = (Entity) tmp.get(j);

        if (block.intersects(bomb, true))
        {
          bombs.remove(bomb);
        }
      }

      tmp = new ArrayList(lazers);

      // Test de collision des blocs avec les lazers
      for (int j=0; j < tmp.size(); j++)
      {
        Entity bomb = (Entity) tmp.get(j);

        if (block.intersects(bomb, false))
        {
          lazers.remove(bomb);
        }
      }

      tmp = new ArrayList(aliens);

      // Test de collision des blocs avec les aliens
      for (int j=0; j < tmp.size(); j++)
      {
        Entity bomb = (Entity) tmp.get(j);

        if (block.intersects(bomb, false))
        {
          spaceShip.removeLife();
        }
      }
    }
  }

  /// Test de collision des blocs avec les aliens
  /// Si les aliens touchent un bloc, on tue le spaceship
  private void checkAliensCollisions()
  {
    for (int j=0; j < aliens.size(); j++)
    {
      Entity alien = (Entity) aliens.get(j);

      if (spaceShip.checkCollision(alien))
      {
        spaceShip.removeLife();
      }
    }
  }

  private boolean checkCollisions(ArrayList lst, Entity otherEntity)
  {
    boolean hit = false;
    ArrayList tmp = new ArrayList(lst);

    for (int i=0; i < tmp.size(); i++)
    {
      Entity entity = (Entity) tmp.get(i);

      if (entity.checkCollision(otherEntity))
      {
        lst.remove(entity);
        hit = true;
      }
    }

    return hit;
  }

  /// Affichage de toutes les entités
  public void display()
  {
    spaceShip.display();
    if (ufo != null && !ufo.isDead)
    {
      ufo.display();
    }
    display(aliens);
    display(bombs);
    display(lazers);
    display(blocks);


    if (isCheatOn)
    {
      spaceShip.lifes = 15;
    }

    fill(255);
    text("Lives left: " + spaceShip.lifes, 10, 15);
    rect(10, 24, spaceShip.lifes*10, 10);
    text("Score: " + score, 1200, 970);
  }

  private void display(ArrayList lst)
  {
    for (int i=0; i < lst.size(); i++)
    {
      ((Entity) lst.get(i)).display();
    }
  }

  // afficher la page de chargement, uniquement au démarrage du jeu
  public void loadingPage()
  {
    if (!started) {
      image(istart, 0, 0);
    }
  }

  // lance le jeu
  public void startGame()
  {
    if (!started) {
      started = true;
      loop(); //active la boucle
    }
  }

  // met le jeu en pause
  public void pauseGame()
  {
    if (started) {
      if (pause) {
        pause = false;
        loop();
      }
      else {
        pause = true;
        noLoop();
      }
    }
  }
}

